summaryrefslogtreecommitdiffstats
path: root/src/core/hle/kernel/k_light_condition_variable.cpp
blob: 8fce2bc71229ea6ea4778abd9dd1d0d831ef0c9a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/kernel/k_light_condition_variable.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/svc_results.h"

namespace Kernel {

namespace {

class ThreadQueueImplForKLightConditionVariable final : public KThreadQueue {
public:
    ThreadQueueImplForKLightConditionVariable(KernelCore& kernel_, KThread::WaiterList* wl,
                                              bool term)
        : KThreadQueue(kernel_), m_wait_list(wl), m_allow_terminating_thread(term) {}

    void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
        // Only process waits if we're allowed to.
        if (ResultTerminationRequested == wait_result && m_allow_terminating_thread) {
            return;
        }

        // Remove the thread from the waiting thread from the light condition variable.
        m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));

        // Invoke the base cancel wait handler.
        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
    }

private:
    KThread::WaiterList* m_wait_list;
    bool m_allow_terminating_thread;
};

} // namespace

void KLightConditionVariable::Wait(KLightLock* lock, s64 timeout, bool allow_terminating_thread) {
    // Create thread queue.
    KThread* owner = GetCurrentThreadPointer(kernel);
    KHardwareTimer* timer{};

    ThreadQueueImplForKLightConditionVariable wait_queue(kernel, std::addressof(wait_list),
                                                         allow_terminating_thread);

    // Sleep the thread.
    {
        KScopedSchedulerLockAndSleep lk(kernel, std::addressof(timer), owner, timeout);

        if (!allow_terminating_thread && owner->IsTerminationRequested()) {
            lk.CancelSleep();
            return;
        }

        lock->Unlock();

        // Add the thread to the queue.
        wait_list.push_back(*owner);

        // Begin waiting.
        wait_queue.SetHardwareTimer(timer);
        owner->BeginWait(std::addressof(wait_queue));
    }

    // Re-acquire the lock.
    lock->Lock();
}

void KLightConditionVariable::Broadcast() {
    KScopedSchedulerLock lk(kernel);

    // Signal all threads.
    for (auto it = wait_list.begin(); it != wait_list.end(); it = wait_list.erase(it)) {
        it->EndWait(ResultSuccess);
    }
}

} // namespace Kernel